package com.stars.easyms.logger.support.logback.handler;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.*;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import lombok.Getter;
import org.slf4j.MDC;
import org.slf4j.Marker;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.spi.MDCAdapter;

import java.util.Collections;
import java.util.Map;

/**
 * <p>className: EasyMsLoggingEvent</p>
 * <p>description: 重写可重复利用的日志事件类</p>
 *
 * @author guoguifang
 * @date 2019-11-01 14:25
 * @since 1.3.3
 */
@Getter
public final class EasyMsLoggingEvent implements ILoggingEvent {

    private Logger logger;

    private String loggerName;

    private Marker marker;

    private Level level;

    private String threadName;

    private long timeStamp;

    private LoggerContext loggerContext;

    private volatile String formattedMessage;

    private volatile String message;

    private Object[] argumentArray;

    private ThrowableProxy throwableProxy;

    private StackTraceElement[] callerDataArray;

    private Map<String, String> mdcPropertyMap;

    private int appenderHoldCount;

    EasyMsLoggingEvent(Logger logger, Marker marker, Level level, String message, Throwable throwable, Object[] argArray) {
        this.set(logger, marker, level, message, throwable, argArray);
    }

    void set(Logger logger, Marker marker, Level level, String message, Throwable throwable, Object[] argArray) {
        this.setLogger(logger);
        this.marker = marker;
        this.level = level;
        this.setMessageAndParam(message, throwable, argArray);
        this.threadName = Thread.currentThread().getName();
        this.timeStamp = System.currentTimeMillis();
        this.getMDCPropertyMap();
    }

    private void setLogger(Logger logger) {
        if (this.logger != logger) {
            this.logger = logger;
            this.loggerName = logger.getName();
            this.loggerContext = logger.getLoggerContext();
        }
    }

    private void setMessageAndParam(String message, Throwable throwable, Object[] argArray) {
        this.message = message;
        this.argumentArray = argArray;

        if (throwable == null) {
            throwable = extractThrowableAnRearrangeArguments(argArray);
        }

        if (throwable != null) {
            this.throwableProxy = new ThrowableProxy(throwable);
            if (this.loggerContext.isPackagingDataEnabled()) {
                this.throwableProxy.calculatePackagingData();
            }
        }
    }

    private Throwable extractThrowableAnRearrangeArguments(Object[] argArray) {
        Throwable extractedThrowable = EventArgUtil.extractThrowable(argArray);
        if (EventArgUtil.successfulExtraction(extractedThrowable)) {
            this.argumentArray = EventArgUtil.trimmedCopy(argArray);
        }
        return extractedThrowable;
    }

    @Override
    public String getFormattedMessage() {
        if (this.formattedMessage != null) {
            return this.formattedMessage;
        }
        synchronized (this) {
            if (this.formattedMessage == null) {
                if (this.argumentArray != null) {
                    this.formattedMessage = MessageFormatter.arrayFormat(this.message, this.argumentArray).getMessage();
                    this.argumentArray = null;
                } else {
                    this.formattedMessage = this.message;
                }
                this.message = null;
            }
        }
        return this.formattedMessage;
    }

    @Override
    public LoggerContextVO getLoggerContextVO() {
        return this.loggerContext.getLoggerContextRemoteView();
    }

    EasyMsLoggingEvent clear() {
        this.marker = null;
        this.level = null;
        this.threadName = null;
        this.timeStamp = 0;
        this.formattedMessage = null;
        this.message = null;
        this.argumentArray = null;
        this.throwableProxy = null;
        this.mdcPropertyMap = null;
        return this;
    }

    @Override
    public void prepareForDeferredProcessing() {
        // Intentionally blank
    }

    @Override
    public StackTraceElement[] getCallerData() {
        if (callerDataArray == null) {
            callerDataArray = CallerData.extract(new Throwable(), Logger.FQCN, loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
        }
        return callerDataArray;
    }

    @Override
    public boolean hasCallerData() {
        return callerDataArray != null;
    }

    @Override
    public Map<String, String> getMDCPropertyMap() {
        if (mdcPropertyMap == null) {
            MDCAdapter mdc = MDC.getMDCAdapter();
            if (mdc instanceof LogbackMDCAdapter) {
                mdcPropertyMap = ((LogbackMDCAdapter) mdc).getPropertyMap();
            } else {
                mdcPropertyMap = mdc.getCopyOfContextMap();
            }
        }
        if (mdcPropertyMap == null) {
            mdcPropertyMap = Collections.emptyMap();
        }
        return mdcPropertyMap;
    }

    @Override
    public Map<String, String> getMdc() {
        return getMDCPropertyMap();
    }

    void appenderHold() {
        appenderHoldCount++;
    }

    int appenderRelease() {
        return --appenderHoldCount;
    }

}